package cn.com.anysdk.redis.reconnect;

import cn.com.anysdk.redis.event.RedisEventManager;
import cn.com.anysdk.redis.event.RedisReconnectingEvent;
import cn.com.anysdk.redis.exception.RedisConnectionException;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

/**
 * Redis重连管理器
 */
@Slf4j
public class RedisReconnectManager {

    private final RedisEventManager eventManager;
    private final RedisReconnectStrategy reconnectStrategy;
    private final ScheduledExecutorService scheduler;
    private final AtomicInteger retryCount;
    private final AtomicBoolean reconnecting;
    private ScheduledFuture<?> reconnectTask;

    /**
     * 创建重连管理器
     *
     * @param eventManager 事件管理器
     * @param reconnectStrategy 重连策略
     */
    public RedisReconnectManager(RedisEventManager eventManager, RedisReconnectStrategy reconnectStrategy) {
        this.eventManager = eventManager;
        this.reconnectStrategy = reconnectStrategy;
        this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread thread = new Thread(r, "redis-reconnect-thread");
            thread.setDaemon(true);
            return thread;
        });
        this.retryCount = new AtomicInteger(0);
        this.reconnecting = new AtomicBoolean(false);
    }

    /**
     * 开始重连
     *
     * @param source 事件源
     * @param environment 当前环境
     * @param reconnectAction 重连动作
     */
    public void startReconnect(Object source, String environment, Supplier<Boolean> reconnectAction) {
        // 如果已经在重连中，则不再启动新的重连任务
        if (reconnecting.compareAndSet(false, true)) {
            log.info("开始Redis重连，使用策略: {}", reconnectStrategy.getName());
            retryCount.set(0);
            scheduleReconnect(source, environment, reconnectAction);
        } else {
            log.debug("Redis重连已在进行中，忽略此次重连请求");
        }
    }

    /**
     * 停止重连
     */
    public void stopReconnect() {
        if (reconnecting.compareAndSet(true, false)) {
            if (reconnectTask != null && !reconnectTask.isDone()) {
                reconnectTask.cancel(false);
                reconnectTask = null;
            }
            log.info("Redis重连已停止");
        }
    }

    /**
     * 重置重连状态
     */
    public void reset() {
        stopReconnect();
        retryCount.set(0);
        reconnectStrategy.reset();
    }

    /**
     * 关闭重连管理器
     */
    public void shutdown() {
        stopReconnect();
        scheduler.shutdownNow();
    }

    /**
     * 调度重连任务
     */
    private void scheduleReconnect(Object source, String environment, Supplier<Boolean> reconnectAction) {
        int currentRetryCount = retryCount.get();
        int maxRetryCount = reconnectStrategy.getMaxRetryCount();

        // 检查是否达到最大重试次数
        if (maxRetryCount != -1 && currentRetryCount >= maxRetryCount) {
            log.error("Redis重连失败，已达到最大重试次数: {}", maxRetryCount);
            reconnecting.set(false);
            throw new RedisConnectionException.NetworkException(
                    "Redis重连失败，已达到最大重试次数: " + maxRetryCount);
        }

        // 计算下一次重连的等待时间
        long waitTimeMs = reconnectStrategy.getNextWaitTime(currentRetryCount);

        // 发布重连事件
        eventManager.fireReconnectingEvent(new RedisReconnectingEvent(
                source,
                environment,
                currentRetryCount,
                maxRetryCount,
                waitTimeMs,
                reconnectStrategy instanceof ExponentialBackoffReconnectStrategy
                    ? RedisReconnectingEvent.RetryStrategy.EXPONENTIAL_BACKOFF
                    : RedisReconnectingEvent.RetryStrategy.FIXED_INTERVAL
        ));

        // 调度重连任务
        reconnectTask = scheduler.schedule(() -> {
            try {
                log.info("执行第{}次Redis重连尝试...", currentRetryCount + 1);
                boolean success = reconnectAction.get();

                if (success) {
                    log.info("Redis重连成功");
                    reconnecting.set(false);
                } else {
                    log.warn("Redis重连尝试失败，将继续重试");
                    retryCount.incrementAndGet();
                    scheduleReconnect(source, environment, reconnectAction);
                }
            } catch (Exception e) {
                log.error("Redis重连过程中发生异常", e);
                retryCount.incrementAndGet();
                scheduleReconnect(source, environment, reconnectAction);
            }
        }, waitTimeMs, TimeUnit.MILLISECONDS);
    }
}
